
var 
    api = {
        config: {
            /* == alarm
                * datatype - integer
                * units    - minutes
                indicating at what time period remainging in a session to ask the user to extend their session
            */
            alarm: 1
            
            /* == cycle
                * datatype - integer
                * units    - milliseconds
                time between invocations of cycle function
            */
            ,cycle: 10000
            
            /* == duration
                * datatype - integer
                * units    - minutes
                total time of server-side session
            */
            ,duration: 20

            /* == modal
                * datatype - object
                modal dialog API with an implementation of a .show(message, react)
                === .show() arguments
                * message - string (html) - displayed to the user in the dialog
                * reaction - function - fired in reaction to user interaction
            */
            ,modal: promptu
            
            /* == postee
                * datatype - string {url}
                server-side resource to POST to ajaxically to test if the session is still alive
            */
            ,postee: "session.txt"
            
            /* == redirect
                * datatype - string {url}
                location to send the timed-out user to
            */
            ,redirect: ""
           
            // localized verbiage
            ,messages    : {
                 error       : "An error with the browser has prevented the application from extending your session."
                ,fail        : "Sorry, there was an error in the connection with the server; your session might be closed."
                ,goodbye     : "The session has expired. We are sorry for any information loss you may have encountered."
                ,reminder    : "Your session will expire in approximately %r. Would you like to extend your session at this time?"
            }

            ,units       : [ "minutes", "seconds" ]
        }

        // close out the session and let the user know they have lost their session
        ,end: function (silent) {
            _cleanup();
            if (!silent) {
                if (api.config.redirect) {
                    window.location.href = api.config.redirect;
                } else {
                    api.notify(api.config.messages.goodbye);
                }
            }
        }

        // attempt to extend the session by sending a asynchronous request (POST) back to the server
        ,extend: function () {
            var result = "";
            
            if (_xhr) {
                _xhr.open("POST", api.config.postee, false); // POST to avoid caching
                
                try {
                    _xhr.send("");
                    
                    if (_xhr.status !== 200 || _xhr.responseText.substring(0, 4) !== "true") {
                        throw "XHR Request Failed";
                    }
                    
                    api.start();

                    return true;
                } catch (e) {
                    // something went wrong with the xhr request
                    result = api.config.messages.fail;
                }
            } else {
                result = api.config.messages.error;
            }
            
            // if there is a redirect do not show this message as the visual effect will be jarring because
            // this notification will flash the screen quickly; immediately after it is shown it will be gone
            if (!api.config.redirect) {
                api.notify(result);
            }
            
            // run cleanup instead of close; even if the xhr fails the session may still live
            _cleanup();

            return false;
        }

        // custom notification function
        ,notify: function (q, fn) {
            api.config.modal.show(q, fn);
        }

        // ask the user if they would like to extend the session
        ,query: function () {
            api.notify(
                api.config.messages.reminder.replace("%r", api.remaining(true))
                ,function (event) {
                    !api.extend() && api.end();
                }
            );
            api.query.fired = true;
        }

        // remaining miliseconds till timeout
        ,remaining: function (format) {
            var result = (_sessionEnd - _now()) / 1000;

            if (format) {
                // format the time remaining into a more usable string
                if (result <= 60) {
                    result = parseInt(result, 10) + " " + api.config.units[1];
                } else {
                    result = parseInt((result / 60) + 0.5, 10) + " " + api.config.units[0];
                }
            }

            return result;
        }

        // check for developer-defined options
        ,start: function (options) {
            _simple_extend(api.config, options);
            
            // get the millisecond value of the current date-time and add the millisecond value of the total timeout duration
            _sessionEnd = _now() + api.config.duration * 60000;
            
            // be nice to current page; clear previous page configuration
            _cleanup();
            
            // start the self-calling-cycles
            _cycle();
        }

        ,toString: function (full) {

            return (full
                ? "Accessibility enhancement; warning users of impending server timeouts and provide chances to exted a session."
                : "Session Extension API");
        }
    }

    ,_sessionEnd

    ,_timeout

    ,_xhr = new window.XMLHttpRequest();

    
// clear old timeouts
function _cleanup () {
    window.clearTimeout(_timeout);
    api.query.fired = false;
}

// self-calling, short-timeout, function will have better accuracy than a long-duration timeout 
// as well as more fine-grained control of behavior
function _cycle  () {
    var now = _now();
	
    if (now < _sessionEnd) {
        if (!api.query.fired && now > _sessionEnd - api.config.alarm * 60000) {
            api.query();
        }
        
        _timeout = window.setTimeout(_cycle, api.config.cycle);
    } else {
        api.end();
    }
}

// localize a now method
function _now () {
    // rounded to the nearest second
    return Date.parse(Date());
}

// for testing abstract this functionality out of start()
function _simple_extend (orig, ext) {
    for (var prop in ext) {
        if (ext.hasOwnProperty(prop)) {
            orig[prop] = ext[prop];
        }
    }
}

// export the functionality to a global object
// (function (api) { this.Session = api; }(api));
window.Session = api;


